/*
 *  Copyright (C) 2010 Martin Haulrich <mwh.isv@cbs.dk>
 *
 *  This file is part of the MatrixParser package.
 *
 *  The MatrixParser program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.osdtsystem.incparser.featureextraction;

import org.osdtsystem.incparser.data.CONLLSentence;
import org.osdtsystem.incparser.data.CONLLToken;
import org.osdtsystem.incparser.features.FeatureAggregator;
import org.osdtsystem.incparser.features.FeatureHandler;
import org.osdtsystem.incparser.features.FeatureVector;
import org.osdtsystem.incparser.features.FeatureVectorSparse;
import org.osdtsystem.incparser.graphs.ConllAdapter;
import org.osdtsystem.incparser.graphs.FeatureExtractorOutdateListener;
import org.osdtsystem.incparser.graphs.Graph;


/**
 *
 * @author Martin Haulrich (modified by Matthias Buch-Kromann)
 */
public class MstFeatureExtractorUnlabelledMartin implements MstFeatureExtractorUnlabelled {
    FeatureHandler featureHandler;
    ConllAdapter conll;
    int layer = 0;
    int[] layers = {0};

    public MstFeatureExtractorUnlabelledMartin( ConllAdapter adapter, FeatureHandler featureHandler) {
        this.featureHandler = featureHandler;
        this.conll = adapter;
        this.layer = adapter.layer();
        layers[0] = this.layer;
    }

    public final void extract(FeatureAggregator aggregator, Graph graph, int node) {
        int head = graph.localGraph(node, layer).headNode();
        extractDependency(aggregator, graph, node, head);
    }

    public final void extractDependency(FeatureAggregator aggregator, Graph graph, int node, int head) {
        FeatureVector fv = getFeatureVector(conll, conll.conllSentence(), node, head);
        fv.addTo(aggregator);
    }

    public final FeatureVector getFeatureVector(ConllAdapter adapter, CONLLSentence sentence, int dep1, int head1) {
        int dep = dep1 - 1;
        int head = head1 - 1;
        FeatureVector fv = new FeatureVectorSparse();
//        FeatureVector fv = new SparseFeatureVector();
        int dist = Math.abs(head - dep);
        String distBool;
        if (dist > 10) {
            distBool = "10";
        } else if (dist > 5) {
            distBool = "5";
        } else {
            distBool = "" + (dist - 1);
        }

        String dir = (head > dep) ? "LA" : "RA";

//        String dirDist = "|" + dir + "|" + distBool;
        String dirDist = "&" + dir + "&" + distBool;

        int small = (head < dep) ? head : dep;
        int large = (head > dep) ? head : dep;

        addLinearFeatures("POS", sentence, CONLLToken.POSTAG, dirDist, small, large, fv);
        addLinearFeatures("CPOS", sentence, CONLLToken.CPOSTAG, dirDist, small, large, fv);

        CONLLToken headToken = sentence.get(head);
        CONLLToken depToken = sentence.get(dep);
        addTwoObsFeatures("HC",
                headToken.get(CONLLToken.FORM), headToken.get(CONLLToken.POSTAG),
                depToken.get(CONLLToken.FORM), depToken.get(CONLLToken.POSTAG),
                dirDist, fv);

        addTwoObsFeatures("HCA",
                headToken.get(CONLLToken.FORM), headToken.get(CONLLToken.CPOSTAG),
                depToken.get(CONLLToken.FORM), depToken.get(CONLLToken.CPOSTAG),
                dirDist, fv);

        addTwoObsFeatures("HCC",
                headToken.get(CONLLToken.LEMMA), headToken.get(CONLLToken.POSTAG),
                depToken.get(CONLLToken.LEMMA), depToken.get(CONLLToken.POSTAG),
                dirDist, fv);


        addTwoObsFeatures("HCD",
                headToken.get(CONLLToken.LEMMA), headToken.get(CONLLToken.CPOSTAG),
                depToken.get(CONLLToken.LEMMA), depToken.get(CONLLToken.CPOSTAG),
                dirDist, fv);

        String headFeaturesString = headToken.get(CONLLToken.FEATS);
        String[] headFeatures = headFeaturesString.split("\\|");

        String depFeaturesString = depToken.get(CONLLToken.FEATS);
        String[] depFeatures = depFeaturesString.split("\\|");
//        System.err.println(headFeatures.length + "  " + depFeatures.length);

        for (int i = 0; i < headFeatures.length; i++) {
            for (int j = 0; j < depFeatures.length; j++) {
                addTwoObsFeatures("FF" + i + "*" + j,
                        headToken.get(CONLLToken.FORM), headFeatures[i],
                        depToken.get(CONLLToken.FORM), depFeatures[j],
                        dirDist, fv);
                addTwoObsFeatures("LF" + i + "*" + j,
                        headToken.get(CONLLToken.LEMMA), headFeatures[i],
                        depToken.get(CONLLToken.LEMMA), depFeatures[j],
                        dirDist, fv);
            }
        }
        // Martin uses sets!
        fv.toSet();
       return fv;
    }

    private void addLinearFeatures(String type, CONLLSentence sentence, int typeIndex, String dirDist, int first, int second, FeatureVector fv) {
//        System.out.println("HOOH");
        //System.out.println(type + " " + first + " " + second + dirDist);

        String pLeft = first > -1 ? sentence.get(first - 1).tokens.get(typeIndex) : "STR";
        String pRight = second < sentence.words.size() - 1 ? sentence.get(second + 1).tokens.get(typeIndex) : "END";
        String pLeftRight = first < second - 1 ? sentence.get(first + 1).tokens.get(typeIndex) : "MID";
        String pRightLeft = second > first + 1 ? sentence.get(second - 1).tokens.get(typeIndex) : "MID";

        StringBuilder featPos = concat(type, "PC=", 
                sentence.get(first)
                    .tokens.get(typeIndex),
                " ", sentence.get(second)
                    .tokens.get(typeIndex));

        for (int i = first + 1; i < second; i++) {
            String allPos = sconcat(featPos.toString(), " ", sentence.get(i).get(typeIndex));
//            System.out.println(allPos);
            fv.setWeight(featureHandler.feature(allPos), 1);
            fv.setWeight(featureHandler.feature(allPos + dirDist), 1);
//            System.out.println(allPos+dirDist);
        }

        addCorePosFeatures(type + "PT", pLeft, sentence.get(first).tokens.get(typeIndex), pLeftRight,
                pRightLeft, sentence.get(second).tokens.get(typeIndex), pRight,
                dirDist, fv);

    }

    private void addCorePosFeatures(String prefix,
            String leftOf1, String one, String rightOf1,
            String leftOf2, String two, String rightOf2,
            String dirDist, FeatureVector fv) {

        

        fv.setWeight(featureHandler.feature(sconcat(prefix, "=", leftOf1, " ", one, " ", two, "*", dirDist)), 1);

        StringBuilder feat = concat(prefix, "1=", leftOf1, " ", one, " ", two);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat.append(' ').append(rightOf2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2=", leftOf1, " ", two, " ", rightOf2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "3=", leftOf1, " ", one, " ", rightOf2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "4=", one, " ", two, " ", rightOf2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        /////////////////////////////////////////////////////////////
        prefix = "A" + prefix;

        // feature posL posL+1 posR-1 posR
        fv.setWeight(featureHandler.feature(sconcat(prefix, "1=", one, " ", rightOf1, " ", leftOf2, "*", dirDist)), 1);

        feat = concat(feat, prefix, "1=", one, " ", rightOf1, " ", leftOf2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append(' ').append(two);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2=", one, " ", rightOf1, " ", two);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "3=", one, " ", leftOf2, " ", two);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "4=", rightOf1, " ", leftOf2, " ", two);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        ///////////////////////////////////////////////////////////////
        prefix = "B" + prefix;

        //// feature posL-1 posL posR-1 posR
        feat = concat(feat, prefix, "1=", leftOf1, " ", one, " ", leftOf2, " ", two);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        //// feature posL posL+1 posR posR+1
        feat = concat(feat, prefix, "2=", one, " ", rightOf1, " ", two, " ", rightOf2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(dirDist);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

    }

    private final void addTwoObsFeatures(String prefix,
            String item1F1, String item1F2,
            String item2F1, String item2F2,
            String attachDistance,
            FeatureVector fv) {

        StringBuilder feat = concat(prefix, "2FF1=", item1F1);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF1=", item1F1, " ", item1F2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF1=", item1F1, " ", item1F2, " ", item2F2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF1=", item1F1, " ", item1F2, " ", item2F2, " ", item2F1);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF2=", item1F1, " ", item2F1);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF3=", item1F1, " ", item2F2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);


        feat = concat(feat, prefix, "2FF4=", item1F2, " ", item2F1);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF4=", item1F2, " ", item2F1, " ", item2F2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF5=", item1F2, " ", item2F2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF6=", item2F1, " ", item2F2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF7=", item1F2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF8=", item2F1);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

        feat = concat(feat, prefix, "2FF9=", item2F2);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);
        feat.append('*').append(attachDistance);
        fv.setWeight(featureHandler.feature(feat.toString()), 1);

    }

    static StringBuilder concat(String... strings) {
        return(concat(new StringBuilder(128)));
    }
    
    static StringBuilder concat(StringBuilder s, String... strings) {
        // Reset string builder
        s.setLength(0);
        
        // Performance hack: according to Java tips, this should work!!!
        if (strings.length == 1) {
            s.append(strings[0]);
        } else if (strings.length == 2) {
            s.append(strings[0]).append(strings[1]);
        } else if (strings.length == 3) {
            s.append(strings[0]).append(strings[1]).append(strings[2]);
        } else if (strings.length == 4) {
            s.append(strings[0]).append(strings[1]).append(strings[2]).append(strings[3]);
        } else if (strings.length == 5) {
            s.append(strings[0]).append(strings[1]).append(strings[2]).append(strings[3]).append(strings[4]);
        } else if (strings.length == 6) {
            s.append(strings[0]).append(strings[1]).append(strings[2]).append(strings[3]).append(strings[4]).append(strings[5]);
        } else {
            // What it really does for all the cases...
            for (int i = 0; i < strings.length; ++i) {
                s.append(strings[i]);
            }
        }
        return(s);
    }

    static StringBuilder sconcatStringBuilder = new StringBuilder(128);
    public static String sconcat(String... strings) {
        sconcatStringBuilder.setLength(0);
        return(concat(sconcatStringBuilder, strings).toString());
    }

    public void addOutdateListener(FeatureExtractorOutdateListener listener) {
    }


    public int[] layers() {
        return layers;
    }

    public void graphCleared(Graph graph) {
    }

    public void intFeatureChanged(Graph graph, int node, int feature, int value, int oldvalue) {
    }

    public void objectFeatureChanged(Graph graph, int node, int feature, Object value, Object oldvalue) {
    }

    public void nodeAdded(Graph graph, int node) {
    }

    public void edgeAdded(Graph graph, int layer, int innode, int outnode, int type) {
    }

    public void edgeRemoved(Graph graph, int layer, int innode, int outnode, int type) {
    }

    public void edgeMoved(Graph graph, int layer, int innode, int oldoutnode, int oldtype, int newoutnode, int newtype) {
    }

    public void edgeTypeChanged(Graph graph, int layer, int innode, int outnode, int oldtype, int newtype) {
    }
}
